﻿using System;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

namespace Dot5Demo.Controls
{
    internal static class NativeMethods
    {
        public static HandleRef NullHandleRef = new HandleRef(null, IntPtr.Zero);

        #region GetSet API

        //SetClassLong won't work correctly for 64-bit: we should use SetClassLongPtr instead.  On
        //32-bit, SetClassLongPtr is just #defined as SetClassLong.  SetClassLong really should
        //take/return int instead of IntPtr/HandleRef, but since we're running this only for 32-bit
        //it'll be OK.
        public static IntPtr SetClassLong(HandleRef hWnd, int nIndex, IntPtr dwNewLong)
        {
            if (IntPtr.Size == 4)
            {
                return SetClassLongPtr32(hWnd, nIndex, dwNewLong);
            }
            return SetClassLongPtr64(hWnd, nIndex, dwNewLong);
        }

        [DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto, EntryPoint = "SetClassLong")]
        [SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable")]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SetClassLongPtr32(HandleRef hwnd, int nIndex, IntPtr dwNewLong);

        [SuppressMessage("Microsoft.Interoperability", "CA1400:PInvokeEntryPointsShouldExist")]
        [DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto, EntryPoint = "SetClassLongPtr")]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SetClassLongPtr64(HandleRef hwnd, int nIndex, IntPtr dwNewLong);

        //SetWindowLong won't work correctly for 64-bit: we should use SetWindowLongPtr instead.  On
        //32-bit, SetWindowLongPtr is just #defined as SetWindowLong.  SetWindowLong really should
        //take/return int instead of IntPtr/HandleRef, but since we're running this only for 32-bit
        //it'll be OK.
        public static IntPtr SetWindowLong(HandleRef hWnd, int nIndex, HandleRef dwNewLong)
        {
            if (IntPtr.Size == 4)
            {
                return SetWindowLongPtr32(hWnd, nIndex, dwNewLong);
            }
            return SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
        }

        [SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable")]
        [DllImport("user32.dll", CharSet = CharSet.Auto, EntryPoint = "SetWindowLong")]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SetWindowLongPtr32(HandleRef hWnd, int nIndex, HandleRef dwNewLong);

        [SuppressMessage("Microsoft.Interoperability", "CA1400:PInvokeEntryPointsShouldExist")]
        [DllImport("user32.dll", CharSet = CharSet.Auto, EntryPoint = "SetWindowLongPtr")]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SetWindowLongPtr64(HandleRef hWnd, int nIndex, HandleRef dwNewLong);

        public static IntPtr GetWindowLong(HandleRef hWnd, int nIndex)
        {
            if (IntPtr.Size == 4)
            {
                return GetWindowLong32(hWnd, nIndex);
            }
            return GetWindowLongPtr64(hWnd, nIndex);
        }

        [SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable")]
        [DllImport("user32.dll", CharSet = CharSet.Auto, EntryPoint = "GetWindowLong")]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr GetWindowLong32(HandleRef hWnd, int nIndex);

        [SuppressMessage("Microsoft.Interoperability", "CA1400:PInvokeEntryPointsShouldExist")]
        [DllImport("user32.dll", CharSet = CharSet.Auto, EntryPoint = "GetWindowLongPtr")]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr GetWindowLongPtr64(HandleRef hWnd, int nIndex);

        public static IntPtr GetClassLong(HandleRef hWnd, int nIndex)
        {
            if (IntPtr.Size == 4)
            {
                return GetClassLongPtr32(hWnd, nIndex);
            }
            return GetClassLongPtr64(hWnd, nIndex);
        }

        [DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto, EntryPoint = "GetClassLong")]
        [SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable")]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr GetClassLongPtr32(HandleRef hwnd, int nIndex);

        [SuppressMessage("Microsoft.Interoperability", "CA1400:PInvokeEntryPointsShouldExist")]
        [DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto, EntryPoint = "GetClassLongPtr")]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr GetClassLongPtr64(HandleRef hwnd, int nIndex);

        #endregion GetSet API

        public static int MAKELONG(int low, int high)
        {
            return (high << 16) | (low & 0xffff);
        }

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        internal static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, TOOLINFO_TOOLTIP lParam);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        internal static extern int SendMessage(IntPtr hWnd, int msg, int wParam, string lParam);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        internal static extern int SendMessage(IntPtr hWnd, int msg, int wParam, [In, Out] ref Rectangle lParam);

        [DllImport("user32.dll")]
        public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter,
            int x, int y, int cx, int cy, int flags);

        public const string TOOLTIPS_CLASS = "tooltips_class32";

        public const int GCL_STYLE = (-26);
        public const int CS_DROPSHADOW = 0x00020000;
        public const int WM_USER = 0x0400;

        public static readonly int TTM_ADDTOOL;
        public static readonly int TTM_SETTITLE;
        public static readonly int TTM_DELTOOL;
        public static readonly int TTM_NEWTOOLRECT;
        public static readonly int TTM_GETTOOLINFO;
        public static readonly int TTM_SETTOOLINFO;
        public static readonly int TTM_HITTEST;
        public static readonly int TTM_GETTEXT;
        public static readonly int TTM_UPDATETIPTEXT;
        public static readonly int TTM_ENUMTOOLS;
        public static readonly int TTM_GETCURRENTTOOL;

        static NativeMethods()
        {
            if (Marshal.SystemDefaultCharSize == 1)
            {
                TTM_ADDTOOL = NativeMethods.TTM_ADDTOOLA;
                TTM_SETTITLE = NativeMethods.TTM_SETTITLEA;
                TTM_DELTOOL = NativeMethods.TTM_DELTOOLA;
                TTM_NEWTOOLRECT = NativeMethods.TTM_NEWTOOLRECTA;
                TTM_GETTOOLINFO = NativeMethods.TTM_GETTOOLINFOA;
                TTM_SETTOOLINFO = NativeMethods.TTM_SETTOOLINFOA;
                TTM_HITTEST = NativeMethods.TTM_HITTESTA;
                TTM_GETTEXT = NativeMethods.TTM_GETTEXTA;
                TTM_UPDATETIPTEXT = NativeMethods.TTM_UPDATETIPTEXTA;
                TTM_ENUMTOOLS = NativeMethods.TTM_ENUMTOOLSA;
                TTM_GETCURRENTTOOL = NativeMethods.TTM_GETCURRENTTOOLA;
            }
            else
            {
                TTM_ADDTOOL = NativeMethods.TTM_ADDTOOLW;
                TTM_SETTITLE = NativeMethods.TTM_SETTITLEW;
                TTM_DELTOOL = NativeMethods.TTM_DELTOOLW;
                TTM_NEWTOOLRECT = NativeMethods.TTM_NEWTOOLRECTW;
                TTM_GETTOOLINFO = NativeMethods.TTM_GETTOOLINFOW;
                TTM_SETTOOLINFO = NativeMethods.TTM_SETTOOLINFOW;
                TTM_HITTEST = NativeMethods.TTM_HITTESTW;
                TTM_GETTEXT = NativeMethods.TTM_GETTEXTW;
                TTM_UPDATETIPTEXT = NativeMethods.TTM_UPDATETIPTEXTW;
                TTM_ENUMTOOLS = NativeMethods.TTM_ENUMTOOLSW;
                TTM_GETCURRENTTOOL = NativeMethods.TTM_GETCURRENTTOOLW;
            }
        }

        public const int
            TTS_ALWAYSTIP = 0x01,
            TTS_NOPREFIX = 0x02,
            TTS_NOANIMATE = 0x10,
            TTS_NOFADE = 0x20,
            TTS_BALLOON = 0x40,
            //TTI_NONE                =0,
            //TTI_INFO                =1,
            TTI_WARNING = 2,
            //TTI_ERROR               =3,
            TTF_IDISHWND = 0x0001,
            TTF_RTLREADING = 0x0004,
            TTF_TRACK = 0x0020,
            TTF_CENTERTIP = 0x0002,
            TTF_SUBCLASS = 0x0010,
            TTF_TRANSPARENT = 0x0100,
            TTF_ABSOLUTE = 0x0080,
            TTDT_AUTOMATIC = 0,
            TTDT_RESHOW = 1,
            TTDT_AUTOPOP = 2,
            TTDT_INITIAL = 3,
            TTM_TRACKACTIVATE = (0x0400 + 17),
            TTM_TRACKPOSITION = (0x0400 + 18),
            TTM_ACTIVATE = (0x0400 + 1),
            TTM_POP = (0x0400 + 28),
            TTM_ADJUSTRECT = (0x400 + 31),
            TTM_SETDELAYTIME = (0x0400 + 3),
            TTM_SETTITLEA = (WM_USER + 32),
            // wParam = TTI_*, lParam = char* szTitle
            TTM_SETTITLEW = (WM_USER + 33),
            // wParam = TTI_*, lParam = wchar* szTitle
            TTM_ADDTOOLA = (0x0400 + 4),
            TTM_ADDTOOLW = (0x0400 + 50),
            TTM_DELTOOLA = (0x0400 + 5),
            TTM_DELTOOLW = (0x0400 + 51),
            TTM_NEWTOOLRECTA = (0x0400 + 6),
            TTM_NEWTOOLRECTW = (0x0400 + 52),
            TTM_RELAYEVENT = (0x0400 + 7),
            TTM_GETTIPBKCOLOR = (0x0400 + 22),
            TTM_SETTIPBKCOLOR = (0x0400 + 19),
            TTM_SETTIPTEXTCOLOR = (0x0400 + 20),
            TTM_GETTIPTEXTCOLOR = (0x0400 + 23),
            TTM_GETTOOLINFOA = (0x0400 + 8),
            TTM_GETTOOLINFOW = (0x0400 + 53),
            TTM_SETTOOLINFOA = (0x0400 + 9),
            TTM_SETTOOLINFOW = (0x0400 + 54),
            TTM_HITTESTA = (0x0400 + 10),
            TTM_HITTESTW = (0x0400 + 55),
            TTM_GETTEXTA = (0x0400 + 11),
            TTM_GETTEXTW = (0x0400 + 56),
            TTM_UPDATE = (0x0400 + 29),
            TTM_UPDATETIPTEXTA = (0x0400 + 12),
            TTM_UPDATETIPTEXTW = (0x0400 + 57),
            TTM_ENUMTOOLSA = (0x0400 + 14),
            TTM_ENUMTOOLSW = (0x0400 + 58),
            TTM_GETCURRENTTOOLA = (0x0400 + 15),
            TTM_GETCURRENTTOOLW = (0x0400 + 59),
            TTM_WINDOWFROMPOINT = (0x0400 + 16),
            TTM_GETDELAYTIME = (0x0400 + 21),
            TTM_SETMAXTIPWIDTH = (0x0400 + 24),
            TTN_POP = -522,
            TTN_SHOW = -521
            ;

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        internal class TOOLINFO_TOOLTIP
        {
            public int cbSize = Marshal.SizeOf(typeof(TOOLINFO_TOOLTIP));
            public int uFlags;
            public IntPtr hwnd;
            public IntPtr uId;
            public Rectangle rect;
            public IntPtr hinst = IntPtr.Zero;
            public IntPtr lpszText;
            public IntPtr lParam = IntPtr.Zero;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct NMHDR
        {
            public IntPtr hwndFrom;
            public IntPtr idFrom; //This is declared as UINT_PTR in winuser.h
            public int code;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct PAINTSTRUCT
        {
            public IntPtr hdc;
            public bool fErase;

            // rcPaint was a by-value RECT structure
            public int rcPaint_left;

            public int rcPaint_top;
            public int rcPaint_right;
            public int rcPaint_bottom;
            public bool fRestore;
            public bool fIncUpdate;
            public int reserved1;
            public int reserved2;
            public int reserved3;
            public int reserved4;
            public int reserved5;
            public int reserved6;
            public int reserved7;
            public int reserved8;
        }

        [DllImport("user32.dll", ExactSpelling = true, EntryPoint = "BeginPaint", CharSet = CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        private static extern IntPtr IntBeginPaint(HandleRef hWnd, [In, Out] ref PAINTSTRUCT lpPaint);

        public static IntPtr BeginPaint(HandleRef hWnd, [In, Out, MarshalAs(UnmanagedType.LPStruct)] ref PAINTSTRUCT lpPaint)
        {
            return IntBeginPaint(hWnd, ref lpPaint);
        }

        [DllImport("user32.dll", ExactSpelling = true, EntryPoint = "EndPaint", CharSet = CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        private static extern bool IntEndPaint(HandleRef hWnd, ref PAINTSTRUCT lpPaint);

        public static bool EndPaint(HandleRef hWnd, [In, MarshalAs(UnmanagedType.LPStruct)] ref PAINTSTRUCT lpPaint)
        {
            return IntEndPaint(hWnd, ref lpPaint);
        }

        [DllImport("user32.dll", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern bool IsWindowVisible(IntPtr hWnd);

        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;

            public RECT(int left, int top, int right, int bottom)
            {
                this.left = left;
                this.top = top;
                this.right = right;
                this.bottom = bottom;
            }

            public RECT(System.Drawing.Rectangle r)
            {
                this.left = r.Left;
                this.top = r.Top;
                this.right = r.Right;
                this.bottom = r.Bottom;
            }

            public static RECT FromXYWH(int x, int y, int width, int height)
            {
                return new RECT(x, y, x + width, y + height);
            }

            public System.Drawing.Size Size
            {
                get
                {
                    return new System.Drawing.Size(this.right - this.left, this.bottom - this.top);
                }
            }
        }

        internal const int
            WS_EX_TOPMOST = 0x00000008,
            WS_EX_TRANSPARENT = 0x00000020,
            WS_EX_TOOLWINDOW = 0x00000080,
            WS_EX_LAYERED = 0x00080000,
            WS_EX_NOACTIVATE = 0x08000000;

        public const int
            SWP_NOSIZE = 0x0001,
            SWP_NOMOVE = 0x0002,
            SWP_NOZORDER = 0x0004,
            SWP_NOACTIVATE = 0x0010,
            SWP_SHOWWINDOW = 0x0040,
            SWP_HIDEWINDOW = 0x0080,
            SWP_DRAWFRAME = 0x0020,
            SWP_FRAMECHANGED = 0x0020,
            SWP_NOOWNERZORDER = 0x0200;

        public const int
            RDW_INVALIDATE = 0x0001,
            RDW_ERASE = 0x0004,
            RDW_ALLCHILDREN = 0x0080,
            RDW_ERASENOW = 0x0200,
            RDW_UPDATENOW = 0x0100,
            RDW_FRAME = 0x0400;

        public static IntPtr HWND_TOP = new IntPtr(0);
        public static IntPtr HWND_BOTTOM = new IntPtr(1);
        public static IntPtr HWND_TOPMOST = new IntPtr(-1);
        public static IntPtr HWND_NOTOPMOST = new IntPtr(-2);
        public static IntPtr HWND_MESSAGE = new IntPtr(-3);

        [DllImport("user32.dll", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern bool InvalidateRect(IntPtr hWnd, ref NativeMethods.RECT rect, bool erase);

        [DllImport("user32.dll", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern bool InvalidateRect(IntPtr hWnd, NativeMethods.COMRECT rect, bool erase);

        [DllImport("user32.dll", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern bool RedrawWindow(HandleRef hwnd, ref NativeMethods.RECT rcUpdate, HandleRef hrgnUpdate, int flags);

        [DllImport("user32.dll", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern bool RedrawWindow(HandleRef hwnd, NativeMethods.COMRECT rcUpdate, HandleRef hrgnUpdate, int flags);

        [DllImport("user32.dll", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern bool MoveWindow(IntPtr hWnd, int x, int y, int width, int height, bool repaint);

        [DllImport("user32.dll", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern bool AdjustWindowRectEx(ref NativeMethods.RECT lpRect, int dwStyle, bool bMenu, int dwExStyle);

        [StructLayout(LayoutKind.Sequential)]
        public struct WINDOWPOS
        {
            public IntPtr hwnd;
            public IntPtr hwndInsertAfter;
            public int x;
            public int y;
            public int cx;
            public int cy;
            public int flags;
        }

        [StructLayout(LayoutKind.Sequential)]
        public class COMRECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;

            public COMRECT()
            {
            }

            public COMRECT(System.Drawing.Rectangle r)
            {
                this.left = r.X;
                this.top = r.Y;
                this.right = r.Right;
                this.bottom = r.Bottom;
            }

            public COMRECT(int left, int top, int right, int bottom)
            {
                this.left = left;
                this.top = top;
                this.right = right;
                this.bottom = bottom;
            }

            public static COMRECT FromXYWH(int x, int y, int width, int height)
            {
                return new COMRECT(x, y, x + width, y + height);
            }

            public override string ToString()
            {
                return "Left = " + left + " Top " + top + " Right = " + right + " Bottom = " + bottom;
            }
        }
    }
}